#!/usr/bin/python
# Copyright (c) 2018, Juan Manuel Parrilla <jparrill@redhat.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: ipa_vault
author: Juan Manuel Parrilla (@jparrill)
short_description: Manage FreeIPA vaults
description:
  - Add, modify and delete vaults and secret vaults.
  - KRA service should be enabled to use this module.
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  cn:
    description:
      - Vault name.
      - Can not be changed as it is the unique identifier.
    required: true
    aliases: ["name"]
    type: str
  description:
    description:
      - Description.
    type: str
  ipavaulttype:
    description:
      - Vault types are based on security level.
    default: "symmetric"
    choices: ["asymmetric", "standard", "symmetric"]
    aliases: ["vault_type"]
    type: str
  ipavaultpublickey:
    description:
      - Public key.
    aliases: ["vault_public_key"]
    type: str
  ipavaultsalt:
    description:
      - Vault Salt.
    aliases: ["vault_salt"]
    type: str
  username:
    description:
      - Any user can own one or more user vaults.
      - Mutually exclusive with O(service).
    aliases: ["user"]
    type: list
    elements: str
  service:
    description:
      - Any service can own one or more service vaults.
      - Mutually exclusive with O(user).
    type: str
  state:
    description:
      - State to ensure.
    default: "present"
    choices: ["absent", "present"]
    type: str
  replace:
    description:
      - Force replace the existent vault on IPA server.
    type: bool
    default: false
    choices: ["True", "False"]
  validate_certs:
    description:
      - Validate IPA server certificates.
    type: bool
    default: true
extends_documentation_fragment:
  - community.general.ipa.documentation
  - community.general.ipa.connection_notes
  - community.general.attributes
"""

EXAMPLES = r"""
- name: Ensure vault is present
  community.general.ipa_vault:
    name: vault01
    vault_type: standard
    user: user01
    ipa_host: ipa.example.com
    ipa_user: admin
    ipa_pass: topsecret

- name: Ensure vault is present for Admin user
  community.general.ipa_vault:
    name: vault01
    vault_type: standard
    ipa_host: ipa.example.com
    ipa_user: admin
    ipa_pass: topsecret

- name: Ensure vault is absent
  community.general.ipa_vault:
    name: vault01
    vault_type: standard
    user: user01
    state: absent
    ipa_host: ipa.example.com
    ipa_user: admin
    ipa_pass: topsecret

- name: Modify vault if already exists
  community.general.ipa_vault:
    name: vault01
    vault_type: standard
    description: "Vault for test"
    ipa_host: ipa.example.com
    ipa_user: admin
    ipa_pass: topsecret
    replace: true

- name: Get vault info if already exists
  community.general.ipa_vault:
    name: vault01
    ipa_host: ipa.example.com
    ipa_user: admin
    ipa_pass: topsecret
"""

RETURN = r"""
vault:
  description: Vault as returned by IPA API.
  returned: always
  type: dict
"""

import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec


class VaultIPAClient(IPAClient):
    def __init__(self, module, host, port, protocol):
        super().__init__(module, host, port, protocol)

    def vault_find(self, name):
        return self._post_json(method="vault_find", name=None, item={"all": True, "cn": name})

    def vault_add_internal(self, name, item):
        return self._post_json(method="vault_add_internal", name=name, item=item)

    def vault_mod_internal(self, name, item):
        return self._post_json(method="vault_mod_internal", name=name, item=item)

    def vault_del(self, name):
        return self._post_json(method="vault_del", name=name)


def get_vault_dict(description=None, vault_type=None, vault_salt=None, vault_public_key=None, service=None):
    vault = {}

    if description is not None:
        vault["description"] = description
    if vault_type is not None:
        vault["ipavaulttype"] = vault_type
    if vault_salt is not None:
        vault["ipavaultsalt"] = vault_salt
    if vault_public_key is not None:
        vault["ipavaultpublickey"] = vault_public_key
    if service is not None:
        vault["service"] = service
    return vault


def get_vault_diff(client, ipa_vault, module_vault, module):
    return client.get_diff(ipa_data=ipa_vault, module_data=module_vault)


def ensure(module, client):
    state = module.params["state"]
    name = module.params["cn"]
    # user = module.params["username"]  TODO is this really not needed?
    replace = module.params["replace"]

    module_vault = get_vault_dict(
        description=module.params["description"],
        vault_type=module.params["ipavaulttype"],
        vault_salt=module.params["ipavaultsalt"],
        vault_public_key=module.params["ipavaultpublickey"],
        service=module.params["service"],
    )
    ipa_vault = client.vault_find(name=name)

    changed = False
    if state == "present":
        if not ipa_vault:
            # New vault
            changed = True
            if not module.check_mode:
                ipa_vault = client.vault_add_internal(name, item=module_vault)
        else:
            # Already exists
            if replace:
                diff = get_vault_diff(client, ipa_vault, module_vault, module)
                if len(diff) > 0:
                    changed = True
                    if not module.check_mode:
                        data = {}
                        for key in diff:
                            data[key] = module_vault.get(key)
                        client.vault_mod_internal(name=name, item=data)

    else:
        if ipa_vault:
            changed = True
            if not module.check_mode:
                client.vault_del(name)

    return changed, client.vault_find(name=name)


def main():
    argument_spec = ipa_argument_spec()
    argument_spec.update(
        cn=dict(type="str", required=True, aliases=["name"]),
        description=dict(type="str"),
        ipavaulttype=dict(
            type="str", default="symmetric", choices=["standard", "symmetric", "asymmetric"], aliases=["vault_type"]
        ),
        ipavaultsalt=dict(type="str", aliases=["vault_salt"]),
        ipavaultpublickey=dict(type="str", aliases=["vault_public_key"]),
        service=dict(type="str"),
        replace=dict(type="bool", default=False, choices=[True, False]),
        state=dict(type="str", default="present", choices=["present", "absent"]),
        username=dict(type="list", elements="str", aliases=["user"]),
    )

    module = AnsibleModule(
        argument_spec=argument_spec, supports_check_mode=True, mutually_exclusive=[["username", "service"]]
    )

    client = VaultIPAClient(
        module=module,
        host=module.params["ipa_host"],
        port=module.params["ipa_port"],
        protocol=module.params["ipa_prot"],
    )
    try:
        client.login(username=module.params["ipa_user"], password=module.params["ipa_pass"])
        changed, vault = ensure(module, client)
        module.exit_json(changed=changed, vault=vault)
    except Exception as e:
        module.fail_json(msg=f"{e}", exception=traceback.format_exc())


if __name__ == "__main__":
    main()
